package jpacman.model;

import java.util.ArrayList;



/**
 * A Cell keeps track of its (x,y) position on the board, and the potential
 * Guest occupying the Cell. It's responsibilities include identifying
 * neighboring cells that fall within the Board's borders (to support moving
 * guests on the board), and keeping the Cell-Guest association consistent.
 *
 * @author Arie van Deursen; Jul 27, 2003
 * @version $Id: Cell.java 4263 2011-01-29 13:50:11Z arievandeursen $
 */
public class Cell {

    /**
     * The x and y coordinates of the cell.
     */
    private final int x, y;

    /**
     * The board the cell lives on.
     */
    private final Board board;

    /**
     * The guest occupying the cell, null if not occupied.
     */    
    private final ArrayList<Guest> inhabitants = new ArrayList<Guest>();
    //private Guest inhabitant = null;

    /**
     * Create a new cell at a given position on the board.
     *
     * @param xCoordinate
     *            The X coordinate
     * @param yCoordinate
     *            The Y coordinate
     * @param b
     *            The board
     */
    public Cell(int xCoordinate, int yCoordinate, Board b) {
        x = xCoordinate;
        y = yCoordinate;
        this.board = b;
        assert invariant();
    }

    /**
     * Conjunction of all invariants.
     *
     * @return true iff all invariants hold.
     */
    protected final boolean invariant() {
        return guestInvariant() && boardInvariant();
    }

    /**
     * A Cell should always be part of the Board given at construction.
     *
     * @return true iff this is the case.
     */
    protected final boolean boardInvariant() {
        return board != null && board.withinBorders(x, y);
    }

     /**
     * TODO Invent invariant for the guest association?
     *
     * @return true for the time being.
     */
     protected final boolean guestInvariant() {
    	 // Checks for Guest consistency happen elsewhere and yields variable results
    	return true;
     }

    

    /**
     * Return the inhabitant of this cell.
     *
     * @return The (most recent) Guest hosted by this Cell, 
     *         or null if the Cell is free.
     */
    public Guest getInhabitant() {
    	assert invariant();
    	if (inhabitants.isEmpty()) {
    		return null;
    	}
    	else {
    		return inhabitants.get(inhabitants.size() - 1);
    	}
    }


    /**
     * Modify the guest of this cell. This method is needed by the Guest's
     * occupy method which keeps track of the links in the Cell-Guest
     * association.
     * Precondition: the guest's location should be set at this Cell,
     * and the current cell should not be occupied already
     * by some other guest.
     * <p>
     * Observe that the
     * class invariant doesn't hold at method entry -- therefore it's not a
     * public method. On method exit, however, it is valid again.
     *
     * @param aGuest
     *            The new guest of this cell.
     */
    protected void addGuest(Guest aGuest) {
    	assert aGuest.getLocation() == this;
    	//assert inhabitant == null;
    	//inhabitant = aGuest;
    	
    	inhabitants.add(aGuest);
        assert aGuest.getLocation() == this;
        assert contains(aGuest);
    }

    
    /**
     * Remove the inhabitant from this Cell. This method assumes (precondition)
     * that the cell indeed contains this guests, and that the inhabitant (guest)
     * has already removed its link to this cell.
     * (Only) to be used by Guest.deoccupy().
     * <p>
     * Upon method entry, the class invariant doesn't hold, but on
     * method exit it does.
     *
     * @param aGuest The guest to be removed.
     */
    protected void removeGuest(Guest aGuest) {
    	// Location of guest should NOT be the same as this cell's location, 
        // Whether the guest's location is null should be handled in Guest.deoccupy
    	assert aGuest.getLocation() != this; //!this.contains(aGuest); 
    	assert contains(aGuest);
        //inhabitant = null;
    	inhabitants.remove(inhabitants.indexOf(aGuest));
    	
    	assert aGuest.getLocation() != this;
        //assert inhabitant == null;
    }

    
    /**
     * Determine if the guest is one of the inhabitants
     * of this cell.
     * 
     * @param aGuest Guest that may be here
     * @return true iff aGuest is on this cell.
     */
    public boolean contains(Guest aGuest) {
    	assert invariant();
        assert aGuest != null;
        return inhabitants.contains(aGuest);
    }


    /**
     * Get the horizontal position of this cell.
     *
     * @return the cell's X coordinate
     */
    public int getX() {
        assert invariant();
        return x;
    }

    /**
     * Get the vertical position of this cell.
     *
     * @return the cell's Y coordinate.
     */
    public int getY() {
        assert invariant();
        return y;
    }

    /**
     * Return the cell located at position (x + dx, y + dy), or null if this
     * cell would fall beyond the borders of the Board.
     *
     * @param dx
     *            The x offset
     * @param dy
     *            The y offset
     * @return null or the cell at (x+dx,y+dy).
     */
    public Cell cellAtOffset(int dx, int dy) {
        assert invariant();
        assert Math.abs(dx) < getBoard().getWidth();
        assert Math.abs(dy) < getBoard().getHeight();
        
        Cell result = null;
        int newx = x + dx;
        int newy = y + dy;

        if (newx < 0 || newx >= getBoard().getWidth()) {
        	newx = getBoard().getWidth() - Math.abs(newx);
        	//This doesnt work.. modulus implementation is different with negative values
        	//newx = newx  % getBoard().getWidth();
        }
        if (newy < 0 || newy >= getBoard().getHeight()) {
        	newy = getBoard().getHeight() - Math.abs(newy);
        }

        result = getBoard().getCell(newx, newy);
        assert getBoard().withinBorders(newx, newy);        
        assert result != null;
        assert invariant();
        return result;
    }
    

    /**
     * Return the board this cell is part of.
     *
     * @return This cell's board.
     */
    public Board getBoard() {
        assert invariant();
        return board;
    }
    
    
    /**
     * Determine if the other cell is an immediate neighbor of the current cell.
     * @param otherCell a different cell from this cell
     * @return true if the other cell is immediate adjacent.
     */
    public boolean adjacent(Cell otherCell) {
    	//preconditions
    	assert invariant();
    	assert otherCell != null;
    	assert getBoard() == otherCell.getBoard();
    	assert this != otherCell;
    	
    	
    	int absX = Math.abs(otherCell.getX() - this.getX());
    	int absY = Math.abs(otherCell.getY() - this.getY());
    	boolean ret = (absX == 1 || absY == 1) && !(absX == 1 && absY == 1);
    	
    	//post condition
    	assert invariant();
    	return ret;
    }
    
        
    /** 
     * @return [x,y]@<guest code> string representation.
     */
    @Override
    public String toString() {
        final String location = "[" + x + "," + y + "]";
        char inh = Guest.EMPTY_TYPE;
        if (getInhabitant() != null) {
            inh = getInhabitant().guestType();
        }
        return inh + "@" + location;
    }
}
